/**
 * 
 */
package edu.hm.compiler.scanner;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackReader;
import java.util.Stack;

import edu.hm.compiler.I18NCompiler;
import edu.hm.compiler.scanner.dfa.IDFA;
import edu.hm.compiler.scanner.dfa.state.IState;
import edu.hm.compiler.scanner.dfa.state.impl.EndState;
import edu.hm.compiler.scanner.dfa.state.impl.ErrorState;
import edu.hm.compiler.scanner.dfa.symbol.Char;
import edu.hm.compiler.scanner.token.ErrorToken;
import edu.hm.compiler.scanner.token.IToken;
import edu.hm.compiler.scanner.token.Token;

/**
 * @author Saruman
 */
public class Driver implements IScanner
{

    private final IDFA            dfa;
    private final PushbackReader  input;
    private int                   col        = 0;
    private int                   row        = 0;
    private final Stack< IToken > ungetStack = new Stack<>();

    /**
     * @param dfa
     * @param input
     * @throws IOException
     */
    public Driver( IDFA dfa, InputStream input ) throws IOException
    {
        if( dfa == null )
            throw new IllegalArgumentException( I18NCompiler.getString("DFANotNull") ); //$NON-NLS-1$
        if( input == null )
            throw new IllegalArgumentException( I18NCompiler.getString("InputNotNull") ); //$NON-NLS-1$
        this.dfa = dfa;
        this.input = new PushbackReader( new InputStreamReader( input ), input.available() );
    }

    /*
     * (non-Javadoc)
     * @see edu.hm.compiler.scanner.IScanner#get()
     */
    @Override
    public IToken get() throws IOException
    {
        this.dfa.reset();
        if( !this.ungetStack.isEmpty() )
            return this.ungetStack.pop();
        IState nextState = null;
        IToken lastToken = null;
        String pattern = ""; //$NON-NLS-1$
        while( !( nextState instanceof ErrorState ) && this.input.ready() )
        {
            int read = this.input.read();
            this.col++ ;
            if( read == '\n' )
            {
                this.row++ ;
                read = ' ';
            }
            else if( read == '\r' )
            {
                this.col = 0;
                read = ' ';
            }
            pattern += (char) read;
            nextState = this.dfa.getNextState( new Char( read, this.row, this.col ) );
            if( nextState instanceof EndState )
                lastToken = new Token( nextState.getTokenClass(), pattern, this.row, this.col - pattern.length() );
        }
        if( lastToken != null )
        {
            int ll = lastToken.getPattern().length();
            this.col -= pattern.length() - ll;
            this.input.unread( pattern.substring( ll ).toCharArray() );
        } else
        {
            return new ErrorToken( pattern );
        }
        return lastToken;
    }

    /*
     * (non-Javadoc)
     * @see edu.hm.compiler.scanner.IScanner#unget(edu.hm.compiler.IToken)
     */
    @Override
    public IScanner unget( IToken token )
    {
        this.ungetStack.push( token );
        return this;
    }

    /*
     * (non-Javadoc)
     * @see edu.hm.compiler.scanner.IScanner#hasNext()
     */
    @Override
    public boolean hasNext()
    {
        try
        {
            return !this.ungetStack.isEmpty() || this.input.ready();
        } catch( IOException e )
        {
            return false;
        }
    }

}
